# -*- coding: utf-8 -*-
"""
    xfree.auth.services.authentication
    ~~~~~~~~~~~~~~~~~~~~~
    文件描述：xfree中的认证提供者、处理程序和后处理器
    作者：xfree
    邮箱：cy_83k@qq.com
"""

import logging
from datetime import datetime

import attr
from flask_babelplus import gettext as _
from pytz import UTC
from werkzeug.security import check_password_hash

# 从相关模块导入各种认证相关的类和函数
from...core.auth.authentication import (
    AuthenticationFailureHandler,
    AuthenticationManager,
    AuthenticationProvider,
    PostAuthenticationHandler,
    StopAuthentication,
)
from...extensions import db
from...user.models import User
from...utils.helpers import time_utcnow

# 获取日志记录器
logger = logging.getLogger(__name__)


@attr.s(frozen=True)
class FailedLoginConfiguration(object):
    """
    用于配置在账户被临时锁定之前允许的失败登录次数，以及账户临时锁定的时长。
    """

    limit = attr.ib()
    lockout_window = attr.ib()


class BlockTooManyFailedLogins(AuthenticationProvider):
    """
    这是一个认证前检查类，用于阻止来自具有过多失败登录尝试的账户的登录操作。
    """

    def __init__(self, configuration):
        self.configuration = configuration

    def authenticate(self, identifier, secret):
        """
        进行认证操作

        :param identifier: 用于识别用户的标识（用户名或邮箱）
        :param secret: 用户的密码或其他认证密钥
        """
        # 根据用户名或邮箱查询用户
        user = User.query.filter(
            db.or_(User.username == identifier, User.email == identifier)
        ).first()

        if user is not None:
            attempts = user.login_attempts
            last_attempt = user.last_failed_login or datetime.min.replace(tzinfo=UTC)
            reached_attempt_limit = attempts >= self.configuration.limit
            inside_lockout = (
                last_attempt + self.configuration.lockout_window
            ) >= time_utcnow()

            if reached_attempt_limit and inside_lockout:
                # 如果达到尝试次数限制且在锁定时间内，抛出阻止认证异常
                raise StopAuthentication(
                    _(
                        "Your account is currently locked out due to too many "
                        "failed login attempts"
                    )
                )


class DefaultxfreeAuthProvider(AuthenticationProvider):
    """
    这是默认的用户名/邮箱和密码认证检查器，根据传入的标识符（用户名或邮箱）定位用户，
    并将提供的密码与匹配用户（如果有）的哈希值进行比较。
    它提供了防止基于密码哈希不匹配导致响应时间差异的定时攻击的保护。
    """

    def authenticate(self, identifier, secret):
        """
        进行认证操作

        :param identifier: 用于识别用户的标识（用户名或邮箱）
        :param secret: 用户的密码或其他认证密钥
        """
        # 根据用户名或邮箱查询用户
        user = User.query.filter(
            db.or_(User.username == identifier, User.email == identifier)
        ).first()

        if user is not None:
            if check_password_hash(user.password, secret):
                return user
            return None

        # 这是为了防止定时攻击而添加的虚拟密码检查
        check_password_hash("dummy password", secret)
        return None


class MarkFailedLogin(AuthenticationFailureHandler):
    """
    这是一个认证失败处理程序，用于在用户记录上标记登录尝试，并设置最后一次失败登录的时间。
    """

    def handle_authentication_failure(self, identifier):
        """
        处理认证失败操作

        :param identifier: 用于识别用户的标识（用户名或邮箱）
        """
        # 根据用户名或邮箱查询用户
        user = User.query.filter(
            db.or_(User.username == identifier, User.email == identifier)
        ).first()

        if user is not None:
            user.login_attempts += 1
            user.last_failed_login = time_utcnow()


class BlockUnactivatedUser(PostAuthenticationHandler):
    """
    这是一个认证后处理程序，用于阻止已通过认证检查但尚未激活账户的用户。
    """

    def handle_post_auth(self, user):
        """
        处理认证后操作

        :param user: 已认证的用户
        """
        if not user.activated:  # pragma: no branch
            # 如果用户未激活，抛出阻止认证异常
            raise StopAuthentication(
                _(
                    "In order to use your account you have to "
                    "activate it through the link we have sent to "
                    "your email address."
                )
            )


class ClearFailedLogins(PostAuthenticationHandler):
    """
    这是一个认证后处理程序，用于清除用户账户上所有失败的登录尝试记录。
    """

    def handle_post_auth(self, user):
        """
        处理认证后操作

        :param user: 已认证的用户
        """
        user.login_attempts = 0


class PluginAuthenticationManager(AuthenticationManager):
    """
    这是一个依赖插件钩子来管理认证过程的认证管理器，它是xfree的默认认证管理器。
    """

    def __init__(self, plugin_manager, session):
        self.plugin_manager = plugin_manager
        self.session = session

    def authenticate(self, identifier, secret):
        """
        进行认证操作

        :param identifier: 用于识别用户的标识（用户名或邮箱）
        :param secret: 用户的密码或其他认证密钥
        """
        try:
            user = self.plugin_manager.hook.xfree_authenticate(
                identifier=identifier, secret=secret
            )
            if user is None:
                raise StopAuthentication(_("Wrong username or password."))
            self.plugin_manager.hook.xfree_post_authenticate(user=user)
            return user
        except StopAuthentication:
            self.plugin_manager.hook.xfree_authentication_failed(
                identifier=identifier
            )
            raise
        finally:
            try:
                self.session.commit()
            except Exception:
                logger.exception("Exception while processing login")
                self.session.rollback()
                raise